Cloud Functionsから別のCloud Functionsを呼び出せるようにDeployment Managerで権限設定してみた
CX事業本部@大阪の岩田です。Cloud Functionsはデフォルトで「App Engineデフォルトサービスアカウント」を利用しますが、明示的に自作したサービスアカウントを利用したいケースもあると思います。今回サービスアカウントとCloud Functionsの作成に加えて、作成したサービスアカウントから別のCloud Functionsを呼び指す権限を設定するまでをDeployment Managerで自動化してみたので、ハマりどころを含めて紹介させて頂きます。
1.Cloud Functionsの呼び出し権限を設定せずにデプロイしてみる
まずは呼び出し元の関数と呼び出される側の関数をそれぞれ準備します。
まず呼び出される側です。
def handler(event, conttext): print('receiver')
ログを出力するだけのシンプルな内容です。こちらは後ほどreceiver
という名前でデプロイします。
続いて呼び出し側です
from google.cloud.functions_v1 import CloudFunctionsServiceClient PROJECT_ID = '<対象プロジェクトID>' def handler(event, conttext): client = CloudFunctionsServiceClient() func_name = client.cloud_function_path(PROJECT_ID, 'asia-northeast1', 'receiver') res = client.call_function(name=func_name, data='{}') print(res)
receiver
を起動するだけのシンプルな内容です。ではこれら2つの関数と関連するリソースをDeployment Managerでデプロイしましょう。事前にそれぞれのソースコードをZIP化し、
- invoker.zip
- receiver.zip
という名前で適当なストレージバケットにアップロードしていることが前提になります。ZIPの準備ができたら以下のテンプレートからまとめてデプロイします。
resources: - type: pubsub.v1.topic name: pubsub-topic properties: topic: test-topic - type: gcp-types/iam-v1:projects.serviceAccounts name: service-account properties: accountId: cloud-functions-invoker displayName: Service Account For Invoke Cloud Functions - type: gcp-types/cloudfunctions-v1:projects.locations.functions name: invoker properties: parent: projects/<プロジェクトID>/locations/asia-northeast1 function: invoker sourceArchiveUrl: gs://<適当なストレージバケット>/invoker.zip serviceAccountEmail: $(ref.service-account.email) entryPoint: handler runtime: python39 eventTrigger: resource: $(ref.pubsub-topic.name) eventType: providers/cloud.pubsub/eventTypes/topic.publish - type: gcp-types/cloudfunctions-v1:projects.locations.functions name: receiver properties: parent: projects/<プロジェクトID>/locations/asia-northeast1 function: receiver sourceArchiveUrl: gs://<適当なストレージバケット>/receiver.zip entryPoint: handler runtime: python39 eventTrigger: resource: $(ref.pubsub-topic.name) eventType: providers/cloud.pubsub/eventTypes/topic.publish
gcloud CLIを使ってデプロイします
gcloud deployment-manager deployments create cf-test --config resources.yml
デプロイできたらinvokerをテスト実行してみましょう
以下のエラーメッセージが表示され、実行に失敗してしまいます
Error: function terminated. Recommended action: inspect logs for termination reason. Additional troubleshooting documentation can be found at https://cloud.google.com/functions/docs/troubleshooting#logging Details: 500 Internal Server Error: The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
ログの詳細を確認すると、以下のようにPermission 'cloudfunctions.functions.call' denied on resource 'projects/<プロジェクト名>/locations/asia-northeast1/functions/receiver' (or resource may not exist).
というエラーメッセージが出力されていることが分かります。
エラーメッセージから、Deployment Managerの構成ファイル内でCloud Functions用に作成したサービスアカウントが別のCloud Functionsを呼び出す権限を持っていないことが原因と推測できます。
2.Deployment ManagerのaccessControlセクションを使って権限を追加してみる
原因がわかったので、Cloud Functions用のサービスアカウントにcloudfunctions.developer
のロールを割り当てましょう。今回デプロイしている関数はHTTPトリガーではないので、権限としてはcloudfunctions.functions.call
の権限が必要になります
Cloud Functions IAM Permissions
そのため、指定するロールはcloudfunctions.functions.call
の権限を持つroles/cloudfunctions.developer
のロールを指定します。
Deplyment ManagerのaccessControl
を利用して以下のように設定を追加します。
- type: gcp-types/cloudfunctions-v1:projects.locations.functions name: receiver properties: parent: projects/<プロジェクトID>/locations/asia-northeast1 ...略 accessControl: gcpIamPolicy: bindings: - role: roles/cloudfunctions.developer members: - serviceAccount:$(ref.service-account.email)
構成ファイルが準備できたのでデプロイします
gcloud deployment-manager deployments update cf-test --config resources.yml
これでデプロイ完了すれば先程の権限エラーが解消されるはず...と思いきやこんなエラーが発生してしまいました
The fingerprint of the deployment is b'-FahLD1dFHh7hG29Y4yh-w==' Waiting for update [operation-1630244417139-5cab2db617393-a5e426c8-7bd2e65c]...failed. ERROR: (gcloud.deployment-manager.deployments.update) Error in Operation [operation-1630244417139-5cab2db617393-a5e426c8-7bd2e65c]: errors: - code: RESOURCE_ERROR location: /deployments/cf-test/resources/receiver message: '{"ResourceType":"gcp-types/cloudfunctions-v1:projects.locations.functions","ResourceErrorCode":"404","ResourceErrorMessage":{"statusMessage":"Not Found","requestPath":"https://cloudfunctions.googleapis.com/v1/:setIamPolicy","httpMethod":"POST"}}'
3.gcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding
を使って権限を追加してみる
先程のエラーメッセージをググってみると、こんなissueを発見しました
setIamPolicy for Cloud Functions #494
Cloud FunctionsのAPI仕様的にaccessControlセクションが利用できないので、代替策としてgcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding
のリソースをデプロイして、サービスアカウントにロールを割り当てる必要があるそうです。issueを参考にDeployment Managerの構成ファイルに以下の記述を追加します
- name: binding-cf-developer type: gcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding properties: resource: $(ref.receiver.name) role: roles/cloudfunctions.developer member: serviceAccount:$(ref.service-account.email)
構成ファイルの全体は以下のようになります
resources: - type: pubsub.v1.topic name: pubsub-topic properties: topic: test-topic - type: gcp-types/iam-v1:projects.serviceAccounts name: service-account properties: accountId: cloud-functions-invoker displayName: Service Account For Invoke Cloud Functions - type: gcp-types/cloudfunctions-v1:projects.locations.functions name: invoker properties: parent: projects/<プロジェクトID>/locations/asia-northeast1 function: invoker sourceArchiveUrl: gs://<適当なストレージバケット>/invoker.zip serviceAccountEmail: $(ref.service-account.email) entryPoint: handler runtime: python39 eventTrigger: resource: $(ref.pubsub-topic.name) eventType: providers/cloud.pubsub/eventTypes/topic.publish - type: gcp-types/cloudfunctions-v1:projects.locations.functions name: receiver properties: parent: projects/<プロジェクトID>/locations/asia-northeast1 function: receiver sourceArchiveUrl: gs://<適当なストレージバケット>/receiver.zip entryPoint: handler runtime: python39 eventTrigger: resource: $(ref.pubsub-topic.name) eventType: providers/cloud.pubsub/eventTypes/topic.publish - name: binding-cf-developer type: gcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding properties: resource: $(ref.receiver.name) role: roles/cloudfunctions.developer member: serviceAccount:$(ref.service-account.email)
再度デプロイを実行します
gcloud deployment-manager deployments update cf-test --config resources.yml
すると...
The fingerprint of the deployment is b'7nW3gWkSoKYs9lpVTd1tdA==' Waiting for update [operation-1630245139328-5cab3066d2dbd-94d7bd65-43fae94a]...failed. ERROR: (gcloud.deployment-manager.deployments.update) Error in Operation [operation-1630245139328-5cab3066d2dbd-94d7bd65-43fae94a]: errors: - code: RESOURCE_ERROR location: /deployments/cf-test/resources/binding-cf-developer message: "{\"ResourceType\":\"gcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding\"\ ,\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\"\ :\"Permission 'cloudfunctions.functions.setIamPolicy' denied on resource 'projects/<プロジェクト名>/locations/asia-northeast1/functions/receiver'\ \ (or resource may not exist).\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\"\ :\"Forbidden\",\"requestPath\":\"https://cloudfunctions.googleapis.com/v1/projects/<プロジェクト名>/locations/asia-northeast1/functions/receiver:setIamPolicy\"\ ,\"httpMethod\":\"POST\"}}"
今度はこんなエラーが出てしまいました
4.Deployment ManagerにIAM ポリシーを設定するための権限を付与してからリトライする
先程のエラーメッセージをググると、以下の公式ドキュメントに行き着きました。
Deployment Manager に IAM ポリシーを設定するための権限を付与する
Deployment Managerは「Google API サービス アカウント」を利用して他のGoogle APIを呼び出しており、この「Google API サービス アカウント」がroles/editor
のロールしか持たないため、Deployment Managerを利用したIAMポリシーの設定は実行できないそうです。上記公式ドキュメントの手順に従って「Google API サービス アカウント」にroles/owner
の権限を追加してみます。
gcloud projects add-iam-policy-binding <プロジェクトID> \ --member serviceAccount:<プロジェクトNo>@cloudservices.gserviceaccount.com --role roles/owner
権限が付与できたので、再度デプロイを実行します
gcloud deployment-manager deployments update cf-test --config resources.yml
今度は無事に成功しました
The fingerprint of the deployment is b'vfH8XsolpIBEGK0Hgo-nuQ==' Waiting for update [operation-1630247071848-5cab3799d175a-c3b5e8e0-0673592f]...done. Update operation operation-1630247071848-5cab3799d175a-c3b5e8e0-0673592f completed successfully. NAME TYPE STATE ERRORS INTENT binding-cf-developer gcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding COMPLETED [] invoker gcp-types/cloudfunctions-v1:projects.locations.functions COMPLETED [] pubsub-topic pubsub.v1.topic COMPLETED [] receiver gcp-types/cloudfunctions-v1:projects.locations.functions COMPLETED [] service-account gcp-types/iam-v1:projects.serviceAccounts COMPLETED []
改めてinvokerをテスト実行してみましょう
無事テスト実行に成功しました
まとめ
- デフォルトのポリシーではDeployment ManagerからIAMポリシーの設定ができない
- Deployment Managerの
accessControl
ではCloud Functionsの関数にロールのバインディングを設定できない
という点にハマってしまいました。似たような境遇でお悩みの方の参考になれば幸いです。
参考
- https://cloud.google.com/functions/docs/reference/iam/permissions
- https://cloud.google.com/functions/docs/reference/iam/roles
- https://cloud.google.com/deployment-manager/docs/configuration/set-access-control-resources#granting_permis
- https://stackoverflow.com/questions/62576013/how-to-set-iam-policy-to-cloud-function-with-deployment-manager
- https://github.com/GoogleCloudPlatform/deploymentmanager-samples/issues/494